package org.six11.ss.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.filter.ElementFilter;
import org.jdom.filter.Filter;
import org.jdom.input.SAXBuilder;
import org.six11.ss.shared.Message;
import org.six11.util.Debug;
import org.six11.util.adt.MultiState;
import org.six11.util.adt.SynchronizedQueue;
import org.six11.util.io.StreamUtil;

/**
 * 
 * 
 * @author Gabe Johnson <johnsogg@cmu.edu>
 */
public class Router extends HttpServlet {

  public static Router instance = null;

  private Filter msgFilter;
  protected Map<String, AppletMessageHandler> appletMessageHandlers;
  protected Map<String, WebActionHandler> webActionHandlers;
  protected MultiState state;

  public Router() {
    super();
    this.msgFilter = new ElementFilter("msg");

    // intialize the Stellsketch state

    this.state = new MultiState();
    state.setValue(ServerStateNames.SESSION_TO_PERSON, new HashMap<String, String>());
    state.setValue(ServerStateNames.PERSON_TO_QUEUE,
        new HashMap<String, SynchronizedQueue<Message>>());
    state.setValue(ServerStateNames.SESSION_LAST_SEEN, new HashMap<String, Long>());
    state.setValue(ServerStateNames.SKETCH_BUFFERS, new HashMap<String, StringBuffer>());
    state.setValue(ServerStateNames.GAME_TIMER, new Timer());
    state.setValue(ServerStateNames.TASK_TIMER, new Timer());

    // initialize the applet message handlers, invoked from POST requests
    this.appletMessageHandlers = new HashMap<String, AppletMessageHandler>();
    state.setValue(ServerStateNames.HANDLERS, appletMessageHandlers);
    appletMessageHandlers.put("login", new LoginHandler(state));
    appletMessageHandlers.put("logout", new LogoutHandler(state));
    appletMessageHandlers.put("ping", new PingHandler(state));
    appletMessageHandlers.put("pen", new PenHandler(state));
    appletMessageHandlers.put("room", new RoomHandler(state));
    appletMessageHandlers.put("game", new GameHandler(state));
    appletMessageHandlers.put("chat", new ChatHandler(state));
    appletMessageHandlers.put("score", new ScoreHandler(state));
    appletMessageHandlers.put("nick", new NickHandler(state));
    appletMessageHandlers.put("sketchDone", new SketchDoneHandler(state));
    appletMessageHandlers.put("roomWho", new RoomWhoHandler(state));

    // initialize the web action handlers, invoked from GET requests
    this.webActionHandlers = new HashMap<String, WebActionHandler>();
    state.setValue(ServerStateNames.WEB_ACTION_HANDLERS, webActionHandlers);
    webActionHandlers.put("index", new IndexWebActionHandler("index", "/index.jsp", state));
    webActionHandlers.put("create_account", new CreateAccountWebActionHandler("create_account",
        "/create_account.jsp", state));
    webActionHandlers.put("login", new LoginWebActionHandler("login", "/login.jsp", state));
    webActionHandlers.put("themes", new ThemesWebActionHandler("themes", "/themes.jsp", state));
    webActionHandlers.put("logout", new LogoutWebActionHandler("logout", "/logout.jsp", state));
    webActionHandlers.put("browse", new BrowseWebActionHandler("browse", "/browse.jsp", state));
    webActionHandlers.put("play_stellasketch", new PlayWebActionHandler("play_stellasketch",
        "/play.jsp", state));
    webActionHandlers.put("help", new HelpWebActionHandler("help",
        "/help.jsp", state));

    // begin a session reaper that will intermittently log players out if they haven't connected in
    // a while.
    if (!state.hasValue(ServerStateNames.DISCONNECTION_REAPER)) {
      DisconnectionReaper reaper = new DisconnectionReaper(state);
      reaper.go();
      state.setValue(ServerStateNames.DISCONNECTION_REAPER, reaper);
    }

    if (!state.hasValue(ServerStateNames.SCOREBOARD_UPDATER)) {
      Scoreboard scoreboard = new Scoreboard(state);
      scoreboard.go();
      state.setValue(ServerStateNames.SCOREBOARD_UPDATER, scoreboard);
    }
    try {
//      if (getInitParameter("chatLogFile") != null) {
//        state.setValue(ServerStateNames.CHAT_LOG_FILE, getInitParameter("chatLogFile"));
 //     }
      state.setValue(ServerStateNames.CHAT_LOG_FILE, "/var/log/stellasketch_chat");
    } catch (NullPointerException ex) {
      ex.printStackTrace();
      bug("I have no idea how that happened.");
    }

    // expose this instance statically so the web app event listener can find it.
    instance = this;
    important("Created Router Instance. There should only be one.");
  }

  public MultiState getState() {
    return state;
  }

  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {
    String what = req.getParameter(WebActionHandler.ACTION);
    if (what == null || what.equals("")) {
      what = "index";
    }
    if (webActionHandlers.containsKey(what)) {
      webActionHandlers.get(what).handleWithHibernate(req, resp, getServletContext());
    } else {
      important("Bogus web action request for action '" + what + "'");
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
      IOException {

    // the web pages hit the path '/web'. This should be handled by the doGet method but some
    // forms (especially those involving passwords) send data up via POST. But the doGet can parse
    // those parameters anyway.
    if (req.getServletPath().equals("/web")) {
      doGet(req, resp);
      return;
    }
    HttpSession session = req.getSession();

    PrintWriter out = resp.getWriter();
    InputStream in = req.getInputStream();
    try {
      Document msgDoc = new SAXBuilder().build(in);
      List msgList = msgDoc.getRootElement().getContent(msgFilter);
      if (msgList.isEmpty()) {
        bug("User connected but did not send any messages.");
      }
      for (Object msgObj : msgList) {
        Element elm = (Element) ((Element) msgObj).clone();
        elm.detach();
        Message msg = new Message(elm);
        if (appletMessageHandlers.containsKey(msg.getType())) {
          appletMessageHandlers.get(msg.getType()).handleSafely(msg, session.getId(), out);
        }
      }
    } catch (JDOMException ex) {
      ex.printStackTrace();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  private void bug(String what) {
    Debug.out("Router", what);
  }

  private void important(String what) {
    Debug.out(">>  Router", what);
  }

  @Override
  public void destroy() {
    super.destroy();
    Timer timer = (Timer) state.getValue(ServerStateNames.TASK_TIMER);
    timer.cancel();
    important("Stopped task timer.");
  }
}
